using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ODX.Core
{
    /// <summary>
    /// This class delivers method to manage sessions over incoming channels.
    /// channel can be set as global server session or for the each remoting channel.
    /// </summary>
    public static class RemotingSessionManager
    {
        private static Hashtable channelSessions = new Hashtable();
        private static Session globalSession = null;

        /// <summary>
        /// Register global (static) session.
        /// </summary>
        /// <param name="session"><see cref="Session"/> to be registered.</param>
        public static void RegisterChannelSession(Session session)
        {
            globalSession = session;
        }

        /// <summary>
        /// Register a session, linked to a channel
        /// </summary>
        /// <param name="session"><see cref="Session"/> to be registered.</param>
        /// <param name="channelName">name of the chanel the session wil be linked to.</param>
        public static void RegisterChannelSession(Session session, string channelName)
        {
            if (channelName == null || channelName.Length == 0)
            {
                globalSession = session;
            }
            else
            {
                if (session != null)
                    channelSessions[channelName] = session;
                else if (channelSessions.ContainsKey(channelName))
                    channelSessions.Remove(channelName);
            }
        }

        /// <summary>
        /// Gets the globals session registered with <see cref="RemotingSessionManager"/>
        /// </summary>
        /// <returns>Session object or null if <see cref="Session"/> was not registered.</returns>
        public static Session GetChannelSession()
        {
            return globalSession;
        }

        /// <summary>
        /// Gets <see cref="Session"/> for the specified remoting channel.
        /// </summary>
        /// <param name="channelName">Name of the remoting channel.</param>
        /// <returns>channel <see cref="Session"/> of global <see cref="Session"/>
        /// if apropriate channel session was not registered.</returns>
        public static Session GetChannelSession(string channelName)
        {
            if (channelName == null || channelName.Length == 0)
                return globalSession;
            Session session = (Session) channelSessions[channelName];
            if ( session != null )
                return session;
            return globalSession;
        }
    }

    internal class OdxFUtils
    {
        internal static Stream SerializeEntities(object graph)
        {
            //Debugger.Break();
            BinaryFormatter bf = new BinaryFormatter();
            Session tmp = new Session();
            bf.SurrogateSelector = new RemotingSurrogateSelector();
            EntitySurrogateSelector ess = new EntitySurrogateSelector(tmp, null);
            bf.SurrogateSelector.ChainSelector(ess);
            MemoryStream msGraph = new MemoryStream();
            bf.Serialize(msGraph, graph);
            MemoryStream msSes = new MemoryStream();
            bf.Serialize(msSes, tmp);
            msGraph.WriteTo(msSes);
            return msSes;
        }

        internal static object DeserializeEntities(Stream stream, Session globalSession)
        {
            //Debugger.Break();
            try
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                BinaryFormatter bf = new BinaryFormatter();
                Session tmp = (Session) bf.Deserialize(stream);
                tmp.Schema.ExtendedProperties[Session.SessionKey] = tmp;
                bf.SurrogateSelector = new EntitySurrogateSelector(tmp, globalSession);
                object graph = bf.Deserialize(stream);
                return graph;
            }
            finally
            {
                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }
        }

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
                if (asm.FullName == args.Name)
                    return asm;
            return null;
        }
    }

    internal class OdxTransportHeaders : ITransportHeaders
    {
        Hashtable headers = new Hashtable();
        public IEnumerator GetEnumerator()
        {
            return headers.GetEnumerator();
        }

        public object this[object key]
        {
            get { return headers[key]; }
            set { headers[key] = value; }
        }
    }

    internal class EntitySurrogateSelector : ISurrogateSelector
    {
        private readonly Session workSession;
        private ISurrogateSelector next;
        private readonly Session globalSession;

        public EntitySurrogateSelector(Session workSession, Session globalSession)
        {
            this.workSession = workSession;
            this.globalSession = globalSession;
        }

        public Session GlobalSession { get { return globalSession; } }
        public Session WorkSession { get { return workSession; } }

        public void ChainSelector(ISurrogateSelector selector)
        {
            next = selector;
        }

        public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
        {
            if (type.IsSubclassOf(typeof(Entity)))
            {
                selector = this;
                return new EntitySurrogate(this);
            }
            if (next != null)
                return next.GetSurrogate(type, context, out selector);

            selector = null;
            return null;
        }

        public ISurrogateSelector GetNextSelector()
        {
            return next;
        }
    }

    internal class EntitySurrogate : ISerializationSurrogate
    {
        private readonly EntitySurrogateSelector ess;

        public EntitySurrogate(EntitySurrogateSelector ess)
        {
            this.ess = ess;
        }

        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
        {
            Entity e = (Entity)obj;
            info.AddValue("id", e.ID);
            ess.WorkSession.RegisterTypeRecursive(e.GetType());
            ess.WorkSession.Merge(e);
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context,
                                    ISurrogateSelector selector)
        {
            string id = info.GetString("id");
            Entity e = (Entity)obj;
            e.Impersonate(ess.WorkSession, id);

            if (ess.GlobalSession != null)
                return ess.GlobalSession.Merge(e);

            return null;
        }
    }

    /// <summary>
    /// Formatter sink provider to track serialization on the server side.
    /// </summary>
    public class OdxServerFormatterSinkProvider : IServerFormatterSinkProvider
    {
        private IServerChannelSinkProvider next;

        /// <summary>
        /// This constructor is called by remoting infrastructure
        /// </summary>
        /// <param name="d">Unused</param>
        /// <param name="c">Unused</param>
        public OdxServerFormatterSinkProvider(IDictionary d, ICollection c)
        {
            // remove unused warning
            if (d != null) d.ToString();
            if (c != null) c.ToString();
        }

        void IServerChannelSinkProvider.GetChannelData(IChannelDataStore channelData)
        {
        }

        IServerChannelSink IServerChannelSinkProvider.CreateSink(IChannelReceiver channel)
        {
            return new OdxServerFormatterSink(next.CreateSink(channel), channel.ChannelName);
        }

        IServerChannelSinkProvider IServerChannelSinkProvider.Next
        {
            get { return next; }
            set { next = value; }
        }
    }

    internal class OdxServerFormatterSink : IServerChannelSink
    {
        private IServerChannelSink next;
        private readonly string channelName;

        public OdxServerFormatterSink(IServerChannelSink next, string channelName)
        {
            this.next = next;
            this.channelName = channelName;
        }

        public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg,
                                               ITransportHeaders requestHeaders, Stream requestStream,
                                               out IMessage responseMsg, out ITransportHeaders responseHeaders,
                                               out Stream responseStream)
        {
            Session globalSession = RemotingSessionManager.GetChannelSession(channelName);
            requestMsg = (IMessage)OdxFUtils.DeserializeEntities(requestStream, globalSession);
            requestMsg.Properties["__Uri"] = requestHeaders["__RequestUri"];

            next.ProcessMessage(sinkStack, requestMsg, requestHeaders, null, out responseMsg,
                                out responseHeaders, out responseStream);

            responseStream = OdxFUtils.SerializeEntities(responseMsg);
            responseHeaders = new OdxTransportHeaders();

            return ServerProcessing.Complete;
        }

        public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, object state, IMessage msg,
                                         ITransportHeaders headers, Stream stream)
        {
            throw new NotImplementedException();
        }

        public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, object state, IMessage msg,
                                        ITransportHeaders headers)
        {
            throw new NotImplementedException();
        }

        public IServerChannelSink NextChannelSink
        {
            get { return next; }
        }

        public IDictionary Properties
        {
            get { return null; }
        }
    }


    /// <summary>
    /// Formatter sink provider to track serialization on the client side.
    /// </summary>
    public class OdxClientFormatterSinkProvider : IClientFormatterSinkProvider
    {
        private IClientChannelSinkProvider next;

        /// <summary>
        /// This constructor is called by remoting infrastructure
        /// </summary>
        /// <param name="d">Unused</param>
        /// <param name="c">Unused</param>
        public OdxClientFormatterSinkProvider(IDictionary d, ICollection c)
        {
            // remove unused warning
            if (d != null) d.ToString();
            if (c != null) c.ToString();
        }

        IClientChannelSink IClientChannelSinkProvider.CreateSink(IChannelSender channel, string url, object remoteChannelData)
        {
            return new OdxClientFormatterSink(next.CreateSink(channel, url, remoteChannelData), channel.ChannelName);
        }

        IClientChannelSinkProvider IClientChannelSinkProvider.Next
        {
            get { return next; }
            set { next = value; }
        }
    }

    internal class OdxClientFormatterSink : IClientChannelSink, IMessageSink
    {
        private readonly IClientChannelSink next;
        private readonly string channelName;

        public OdxClientFormatterSink(IClientChannelSink next, string channelName)
        {
            this.next = next;
            this.channelName = channelName;
        }

        public void ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream,
                                   out ITransportHeaders responseHeaders, out Stream responseStream)
        {
            throw new NotImplementedException();
        }

        public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg, ITransportHeaders headers,
                                        Stream stream)
        {
            throw new NotImplementedException();
        }

        public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, object state,
                                         ITransportHeaders headers, Stream stream)
        {
            throw new NotImplementedException();
        }

        public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
        {
            throw new NotImplementedException();
        }

        public IClientChannelSink NextChannelSink
        {
            get { return next; }
        }

        public IDictionary Properties
        {
            get { return null; }
        }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            Stream requestStream = OdxFUtils.SerializeEntities(msg);
            Stream respStream;
            ITransportHeaders headers;
            NextChannelSink.ProcessMessage(msg, new OdxTransportHeaders(), requestStream, out headers, out respStream);
            Session globalSession = RemotingSessionManager.GetChannelSession(channelName);
            return (IMessage)OdxFUtils.DeserializeEntities(respStream, globalSession);
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            throw new NotImplementedException();
        }

        public IMessageSink NextSink
        {
            get { return (IMessageSink)NextChannelSink; }
        }
    }
}